Skip to content

Makefile: suppress ld RWX LOAD segment warning for loader.elf#1406

Open
nyh wants to merge 1 commit into
cloudius-systems:masterfrom
nyh:patch1
Open

Makefile: suppress ld RWX LOAD segment warning for loader.elf#1406
nyh wants to merge 1 commit into
cloudius-systems:masterfrom
nyh:patch1

Conversation

@nyh

@nyh nyh commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

When building loader.elf, ld.bfd now emits:

ld.bfd: warning: build/release.x64/loader.elf has a LOAD segment
with RWX permissions

This warning was introduced in binutils 2.39 (August 2022) as a security-hardening measure to encourage W^X (Write XOR Execute) policies in userspace binaries. Many distros have shipped binutils 2.39 or later since 2023, so the warning now appears for most users.

The warning is a false positive for OSv. Our linker script (arch/x64/loader.ld) deliberately places all sections -- .text, .data, .bss, and friends -- into a single PT_LOAD segment. This is an intentional kernel design: the loader bootstraps the MMU and enforces its own page-level permissions after it is running; the ELF segment permissions are irrelevant at that point.

The "correct" fix would be to split loader.ld into two PT_LOAD segments -- one RX for code and one RW for data -- as W^X would require. However, that change is harder to reason about: OSv's loader relies on the precise VA/PA layout expressed via AT(ADDR(s) - OSV_KERNEL_VM_SHIFT) throughout the linker script, and splitting the single contiguous segment could affect relocation handling, the ELF header placement, and early-boot assumptions that have never been tested with a multi-segment layout. Proper W^X support for the kernel image is tracked in issue #651; we defer it to that effort.

For now, detect whether ld.bfd supports --no-warn-rwx-segments (added in the same binutils 2.39 release that introduced the warning) and, if so, pass it when linking loader.elf and zfs_builder.elf. On older toolchains the flag is absent and the build is unaffected.

When building loader.elf, ld.bfd now emits:

  ld.bfd: warning: build/release.x64/loader.elf has a LOAD segment
          with RWX permissions

This warning was introduced in binutils 2.39 (August 2022) as a
security-hardening measure to encourage W^X (Write XOR Execute)
policies in userspace binaries.  Many distros have shipped binutils
2.39 or later since 2023, so the warning now appears for most users.

The warning is a false positive for OSv.  Our linker script
(arch/x64/loader.ld) deliberately places all sections -- .text, .data,
.bss, and friends -- into a single PT_LOAD segment.  This is an
intentional kernel design: the loader bootstraps the MMU and enforces
its own page-level permissions after it is running; the ELF segment
permissions are irrelevant at that point.

The "correct" fix would be to split loader.ld into two PT_LOAD
segments -- one RX for code and one RW for data -- as W^X would
require.  However, that change is harder to reason about: OSv's loader
relies on the precise VA/PA layout expressed via AT(ADDR(s) -
OSV_KERNEL_VM_SHIFT) throughout the linker script, and splitting the
single contiguous segment could affect relocation handling, the ELF
header placement, and early-boot assumptions that have never been
tested with a multi-segment layout.  Proper W^X support for the kernel
image is tracked in issue cloudius-systems#651; we defer it to that effort.

For now, detect whether ld.bfd supports --no-warn-rwx-segments (added
in the same binutils 2.39 release that introduced the warning) and, if
so, pass it when linking loader.elf and zfs_builder.elf.  On older
toolchains the flag is absent and the build is unaffected.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the build system to suppress the GNU ld.bfd “RWX LOAD segment” warning (introduced in binutils 2.39) when linking OSv’s loader.elf (and zfs_builder.elf), since OSv intentionally uses a single RWX PT_LOAD segment during early boot.

Changes:

  • Adds ld.bfd capability detection for --no-warn-rwx-segments.
  • Conditionally appends --no-warn-rwx-segments to the linker options used for loader.elf and zfs_builder.elf.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Makefile
Comment on lines +91 to +94
ld_no_warn_rwx := $(shell $(LD) --no-warn-rwx-segments 2>&1 | grep -c "unrecognized option")
ifeq ($(ld_no_warn_rwx),0)
conf_linker_extra_options += --no-warn-rwx-segments
endif
@gburd

gburd commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

This one and my #1405 and #1409 are three independent symptoms of the same underlying cause: newer binutils (I hit all three on binutils 2.46) tightened its handling of LOAD segments and section layout, and OSv's linker scripts predate those changes. They do not overlap in the files they touch and none is a substitute for the others; a build on a recent toolchain needs all three. I just built the current master on binutils 2.46 and hit them in this exact order:

  1. build: stop passing loader_options.ld twice to the linker #1409 (Makefile) - hard error: linker script file '.../loader_options.ld' appears multiple times. loader.ld does INCLUDE "loader_options.ld", and the Makefile also lists $(loader_options_dep) in the loader.elf prerequisites, so $^ expands it a second time onto the ld command line. Older ld tolerated the duplicate; 2.46 rejects it. Fix is one line: $(filter-out $(loader_options_dep),$^) so the script is named once (via the INCLUDE) instead of twice. Touches only the Makefile.

  2. lzloader: give .rodata its own output section #1405 (arch/x64/lzloader.ld) - hard error: section .data LMA [00103000,...] overlaps section .rodata.cst16 LMA [00102f90,...]. The old script had no .rodata output section and instead just skipped a fixed gap (. = OSV_LZKERNEL_BASE + 0x3000;) before .data. Newer gcc/binutils emit constant-pool sections (.rodata.cst16, .rodata.str1.*) for the lzloader objects that the old script never placed, so they landed at a default LMA that collides with .data. Fix gives .rodata its own output section (.rodata : { *(.rodata .rodata.*) }) instead of the magic 0x3000 skip. Touches only lzloader.ld.

  3. Makefile: suppress ld RWX LOAD segment warning for loader.elf #1406 (this PR, Makefile) - warning only: loader.elf has a LOAD segment with RWX permissions. OSv deliberately uses a single RWX LOAD segment, so this is noise, and this PR silences it with --no-warn-rwx-segments (gated on support). It does not block the build the way lzloader: give .rodata its own output section #1405/build: stop passing loader_options.ld twice to the linker #1409 do; it just cleans up the output.

So the relationship is: #1409 and #1405 are the two hard link failures that stop a build cold on modern binutils (one in loader.elf, one in lzloader.elf), and #1406 is the cosmetic warning cleanup on top. They are safe to merge in any order and independently, since they touch disjoint locations (Makefile prereq expansion, lzloader section layout, and linker-flag detection respectively). If it helps, I'm happy to fold all three into one "modern-binutils build fixes" PR, but I kept them separate because #1405 also carries the 0x3000 provenance discussion and #1409 is a trivial one-liner.

Separately, on the feature-detection robustness point Copilot raised here: matching the English string "unrecognized option" is locale- and linker-dependent. Scanning $(LD) --help for --no-warn-rwx-segments and gating on that is more robust (no reliance on error-message wording, and it fails closed - if the option isn't listed, it isn't added). Want me to push that variant onto #1409's neighbourhood or send it as a suggestion here?

@gburd

gburd commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

This is worth finishing. binutils 2.39+ emits the warning: <file> has a LOAD segment with RWX permissions on every link, and OSv's single RWX PT_LOAD in arch/x64/loader.ld is intentional (the kernel maps its own text/data/bss in one segment), so the warning is pure noise that will never be actionable here. Suppressing it with --no-warn-rwx-segments when the linker supports it is the right call, and the feature-probe guard means older linkers that lack the flag are unaffected.

One robustness point on the probe, which is also what Copilot flagged: matching the English string "unrecognized option" in the linker's stderr is brittle - it breaks under a localized toolchain or if binutils reworks the diagnostic text. A cleaner detection that doesn't depend on error-message wording:

ld_no_warn_rwx := $(shell $(LD) --help 2>&1 | grep -c -- --no-warn-rwx-segments)
ifneq ($(ld_no_warn_rwx),0)
conf_linker_extra_options += --no-warn-rwx-segments
endif

$(LD) --help lists supported options directly, so this keys off capability rather than failure text and flips the ifeq/ifneq sense accordingly.

Heads up on merge ordering: #1409 also adds lines to the top of the Makefile, so whichever of the two lands second will need a trivial rebase. No textual conflict in the bodies, just adjacent insertions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants